跳到主要内容

C 基础语法学习

关键字

关键字说明
auto声明自动变量
break跳出当前循环
case开关语句分支
char声明字符型变量或函数返回值类型
const定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变
continue结束当前循环,开始下一轮循环
default开关语句中的"其它"分支
do循环语句的循环体
double声明双精度浮点型变量或函数返回值类型
else条件语句否定分支(与 if 连用)
enum声明枚举类型
extern声明变量或函数是在其它文件或本文件的其他位置定义
float声明浮点型变量或函数返回值类型
for一种循环语句
goto无条件跳转语句
if条件语句
int声明整型变量或函数
long声明长整型变量或函数返回值类型
register声明寄存器变量
return子程序返回语句(可以带参数,也可不带参数)
short声明短整型变量或函数
signed声明有符号类型变量或函数
sizeof计算数据类型或变量长度(即所占字节数)
static声明静态变量
struct声明结构体类型
switch用于开关语句
typedef用以给数据类型取别名
unsigned声明无符号类型变量或函数
union声明共用体类型
void声明函数无返回值或无参数,声明无类型指针
volatile说明变量在程序执行中可被隐含地改变
while循环语句的循环条件

预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。 简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。

所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:

指令描述
#define定义宏
#include包含一个源代码文件
#undef取消已定义的宏
#ifdef如果宏已经定义,则返回真
#ifndef如果宏没有定义,则返回真
#if如果给定条件为真,则编译下面代码
#else#if 的替代方案
#elif如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif结束一个 #if……#else 条件编译块
#error当遇到标准错误时,输出错误消息
#pragma使用标准化方法,向编译器发布特殊的命令到编译器中

引用头文件的语法

使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:

// 系统头文件
#include <file>
// 用户头文件
#include "file"

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

如下所示

#ifndef _GLOBAL_H
#define _GLOBAL_H
#include <fstream>
#include <iostream>
#include <math.h>
#include <Config.h>

有时需要从多个不同的头文件中选择一个引用到程序中。例如,需要指定在不同的操作系统上使用的配置参数。

#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif

数据类型

C 中的类型可分为如下几类

类型描述
基本类型它们是算术类型,包括两种类型:整数类型和浮点类型。
枚举类型它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。
void 类型类型说明符 void 表明没有可用的值。
派生类型它们包括:指针类型、数组类型、结构类型、共用体类型和函数类型。

整形

类型存储大小值范围
char1 字节-128 到 127 或 0 到 255
unsigned char1 字节0 到 255
signed char1 字节-128 到 127
int2 或 4 字节-32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int2 或 4 字节0 到 65,535 或 0 到 4,294,967,295
short2 字节-32,768 到 32,767
unsigned short2 字节0 到 65,535
long4 字节-2,147,483,648 到 2,147,483,647
unsigned long4 字节0 到 4,294,967,295
#include <stdio.h>
#include <float.h>

int main()
{
printf("int 存储大小 : %lu \n", sizeof(int));
printf("float 存储最大字节数 : %lu \n", sizeof(float));
printf("float 最小值: %E\n", FLT_MIN );
printf("float 最大值: %E\n", FLT_MAX );
printf("精度值: %d\n", FLT_DIG );

return 0;
}

void 类型

类型描述
函数返回为空C 中有各种函数都不返回值,或者可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status);
函数参数为空C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);
指针指向 void类型为 void 的指针代表对象的地址,而不是类型。例如,内存分配函数 void malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。

强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型

(type_name) expression

使用例,使用强制类型转换运算符把一个整数变量除以另一个整数变量,得到一个浮点数:

#include <stdio.h>

int main()
{
int sum = 17, count = 5;
double mean;

mean = (double) sum / count;
printf("Value of mean : %f\n", mean );

}

这里要注意的是强制类型转换运算符的优先级大于除法,因此 sum 的值首先被转换为 double 型,然后除以 count,得到一个类型为 double 的值。 所以它的结果为:

Value of mean : 3.400000

整数提升

整数提升是指把小于 int 或 unsigned int 的整数类型转换为 int 或 unsigned int 的过程。

#include <stdio.h>

int main()
{
int i = 17;
char c = 'c'; /* ascii 值是 99 */
int sum;

sum = i + c;
printf("Value of sum : %d\n", sum );

}

当上面的代码被编译和执行时,它会产生下列结果:

Value of sum : 116

常用的算术转换

常用的算术转换是隐式地把值强制转换为相同的类型。编译器首先执行整数提升,如果操作数类型不同,则它们会被转换为下列层次中出现的最高层次的类型:(如果一个运算符两边的运算数类型不同,先要将其转换为相同的类型,即较低类型转换为较高类型,然后再参加运算)

usual_arithmetic_conversion.png

#include <stdio.h>

int main()
{
int i = 17;
char c = 'c'; /* ascii 值是 99 */
float sum;

sum = i + c;
printf("Value of sum : %f\n", sum );

}

结果为

Value of sum : 116.000000

在这里,c 首先被转换为整数,但是由于最后的值是 float 型的,所以会应用常用的算术转换,编译器会把 i 和 c 转换为浮点型,并把它们相加得到一个浮点数。

变量

变量的声明有两种情况(定义和声明),一种是定义了变量并分配了内存给它,还有一种则是声明有这个变量,实际上它可以是在其它文件上有定义

extern int i; //声明,不是定义 这里并没有分配内存给 i
int i; //声明,也是定义 这里实际分配了内存给 i

extern 关键字用于在一个文件中引用另一个文件中定义的变量或者函数(其实这个主要是用于引用函数)

例如,如下例子,创建两个文件

addtwonum.c 文件代码:

#include <stdio.h>
/*外部变量声明*/
extern int x ;
extern int y ;
int addTwoNum()
{
return x+y;
}

test.c 文件代码:

#include <stdio.h>

/*定义两个全局变量*/
int x=1;
int y=2;
int addTwoNum();
int main(void)
{
int result;
result = addTwoNum();
printf("result 为: %d\n",result);
return 0;
}

除了上面那种在不同文件使用的例子,extern 还可以用于改变变量的声明位置

#include<stdio.h>

int func();

int main()
{
func(); //1
// 利用 extern 关键字先声明一下 num 变量,告诉编译器 num 这个变量是存在的,但是不是在这之前声明的,你到别的地方找找吧
// 但是如果不存在的话还是会报错的
extern int num;
printf("%d",num); //2
return 0;
}

int num = 3;

int func()
{
printf("%d\n",num);
}

可变参数

需要使用 stdarg.h 头文件,使用步骤如下

  • 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
  • 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  • 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏 va_end 来清理赋予 va_list 变量的内存。
#include <stdio.h>
#include <stdarg.h>

double average(int num,...)
{

va_list valist;
double sum = 0.0;
int i;

/* 为 num 个参数初始化 valist */
va_start(valist, num);

/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);

return sum/num;
}

int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

输出为

Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000

定义常量

整数常量

整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。

212         /* 合法的 */
215u /* 合法的 */
0xFeeL /* 合法的 */
078 /* 非法的:8 不是八进制的数字 */
032UU /* 非法的:不能重复后缀 */

浮点数常量

当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 E 引入的。

3.14159       /* 合法的 */
314159E-5L /* 合法的 */
510E /* 非法的:不完整的指数 */
210f /* 非法的:没有小数或指数 */
.e55 /* 非法的:缺少整数或分数 */

字符常量

这里主要介绍那些转义序列码

转义序列含义
\\ 字符
\'' 字符
\"" 字符
\?? 字符
\a警报铃声
\b退格键
\f换页符
\n换行符
\r回车
\tTab 键
\v垂直制表符
\ooo一到三位的八进制数
\xhh . . .一个或多个数字的十六进制数

定义常量

在 C 中,有两种简单的定义常量的方式:

  1. 使用 #define 预处理器。
  2. 使用 const 关键字。
#define identifier value

如下实例:

#include <stdio.h>

#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'

int main()
{

int area;

area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);

return 0;
}

或者使用 const

const type variable = value;

如下实例:

#include <stdio.h>

int main()
{
const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
int area;

area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);

return 0;
}

存储类

存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C 程序中可用的存储类:

  • auto
  • register
  • static
  • extern

auto 存储类

auto 存储类是所有局部变量默认的存储类。

{
int mount;
auto int month;
}

auto 只能用在函数内,即 auto 只能修饰局部变量。

register 存储类

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小,且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。

register int  miles;

寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中

static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。

#include <stdio.h>

/* 函数声明 */
void func1(void);

static int count=10; /* 全局变量 - static 是默认的 */

int main()
{
while (count--) {
func1();
}
return 0;
}

void func1(void)
{
/* 'thingy' 是 'func1' 的局部变量 - 只初始化一次
* 每次调用函数 'func1' 'thingy' 值不会被重置。
*/
static int thingy=5;
thingy++;
printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}

实例中 count 作为全局变量可以在函数内使用,thingy 使用 static 修饰后,不会在每次调用时重置。

extern 存储类

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。(前面有说过这个 extern 的用途)

第一个文件:main.c

#include <stdio.h>

int count ;
extern void write_extern();

int main()
{
count = 5;
write_extern();
}

第二个文件:support.c

#include <stdio.h>

extern int count;

void write_extern(void)
{
printf("count is %d\n", count);
}

在这里,第二个文件中的 extern 关键字用于声明已经在第一个文件 main.c 中定义的 count。现在 ,编译这两个文件,如下所示:

gcc main.c support.c

这会产生 a.out 可执行程序,当程序被执行时,它会产生下列结果:

count is 5

内存管理

C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。

函数描述
void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
void *malloc(int num);在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
void *realloc(void *address, int newsize);该函数重新分配内存,把内存扩展到 newsize。

void 类型表示未确定类型的指针。C、C++ 规定 void 类型可以通过类型转换强制转换为任何其它类型的指针。

动态分配内存

如下是知道要分配多大的内存

char name[100];

如果预先不知道需要存储的文本长度,例如想存储有关一个主题的详细描述。在这里,我们需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
char name[100];
char *description;

strcpy(name, "Zara Ali");

/* 动态分配内存 */
description = (char *)malloc( 200 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
}

上面的例子还可以使用 calloc() 来编写

calloc(200, sizeof(char));

重新调整内存的大小和释放内存

在不需要内存时,应该调用函数 free() 来释放内存 可以通过调用函数 realloc() 来增加或减少已分配的内存块的大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
char name[100];
char *description;

strcpy(name, "Zara Ali");

/* 动态分配内存 */
description = (char *)malloc( 30 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student.");
}
/* 假设想要存储更大的描述信息 */
description = (char *) realloc( description, 100 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcat( description, "She is in class 10th");
}

printf("Name = %s\n", name );
printf("Description: %s\n", description );

/* 使用 free() 函数释放内存 */
free(description);
}

函数

定义函数的语法

return_type function_name( parameter list )
{
body of the function
}

数组

double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 或者
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

枚举

语法

enum 枚举名 {枚举元素1,枚举元素2,……};

枚举也支持映射元素

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};

// 等价于
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7

也可以更改映射的顺序

// 没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5
enum season {spring, summer=3, autumn, winter};

定义枚举变量

1、先定义枚举类型,再定义枚举变量

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

2、定义枚举类型的同时定义枚举变量

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

3、省略枚举名称,直接定义枚举变量

enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

使用实例

#include <stdio.h>

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};

int main()
{
enum DAY day;
day = WED;
printf("%d",day);
return 0;
}

指针

每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。

#include <stdio.h>

int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */

ip = &var; /* 在指针变量中存储 var 的地址 */

printf("var 变量的地址: %p\n", &var );

/* 在指针变量中存储的地址 */
printf("ip 变量存储的地址: %p\n", ip );

/* 使用指针访问值 */
printf("*ip 变量的值: %d\n", *ip );

return 0;
}

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值

#include <stdio.h>

int main ()
{
int *ptr = NULL;

printf("ptr 的地址是 %p\n", ptr ); // ptr 的地址是 0x0

return 0;
}

空指针可以像 js 那样用来判断(因为空指针就是 16进制的 0)

if(ptr)     /* 如果 p 非空,则完成 */
if(!ptr) /* 如果 p 为空,则完成 */

注意:指针操作结构体要使用 -> 如果非指针则是 .

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

struct Books *struct_pointer;

struct_pointer = &Book1;

// 为了使用指向该结构的指针访问结构的成员,必须使用 -> 运算符
struct_pointer->title;

函数指针

函数指针变量的声明:

// 声明一个指向同样参数、返回值的函数指针类型
typedef int (*fun_ptr)(int, int);

以下实例声明了函数指针变量 p,指向函数 max:

#include <stdio.h>

int max(int x, int y)
{
return x > y ? x : y;
}

int main(void)
{
/* p 是函数指针 */
int (* p)(int, int) = & max; // &可以省略
int a, b, c, d;

printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);

/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);

printf("最大的数字是: %d\n", d);

return 0;
}

回调函数

#include <stdlib.h>  
#include <stdio.h>

// 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}

// 获取随机值
int getNextRandomValue(void)
{
return rand();
}

int main(void)
{
int myarray[10];
/* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}

字符串

char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
// 等价于
char site[] = "RUNOOB";

字符串函数需要引入 #include <string.h>

常用的字符串函数 | 函数 | 目的 | | --------------- | ---------------------------------------------------------------------------------------- | | strcpy(s1, s2); | 复制字符串 s2 到字符串 s1。 | | strcat(s1, s2); | 连接字符串 s2 到字符串 s1 的末尾。 | | strlen(s1); | 返回字符串 s1 的长度。 | | strcmp(s1, s2); | 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。 | | strchr(s1, ch); | 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 | | strstr(s1, s2); | 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |

结构体

定义结构

struct tag { 
member-list
member-list
member-list
...
} variable-list ;

tag 是结构体标签。

member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。

variable-list 结构变量,定义在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量。下面是声明 Book 结构的方式:

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;

有如下三种方式定义结构体

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;


//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;


//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

结构体包含结构体

//此结构体的声明包含了其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};

//此结构体的声明包含了指向自己类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明

struct B;    //对结构体B进行不完整声明

//结构体A中包含指向结构体B的指针
struct A
{
struct B *partner;
//other members;
};

//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A *partner;
//other members;
};

结构体变量的初始化

#include <stdio.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};

int main()
{
printf(
"title : %s \nauthor: %s \nsubject: %s \nbook_id: %d \n",
book.title, book.author, book.subject, book.book_id);
}

访问结构成员

#include <stdio.h>
#include <string.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */

/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;

/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;

/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);

/* 输出 Book2 信息 */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);

return 0;
}

输出的结果为

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

位域

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。

如果程序的结构中包含多个开关量,只有 TRUE/FALSE 变量,如下

struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status;

这种结构需要 8 字节的内存空间,但在实际上,在每个变量中,只存储 0 或 1。在这种情况下,C 语言提供了一种更好的利用内存空间的方式。如果在结构内使用这样的变量,你可以定义变量的宽度来告诉编译器,你将只使用这些字节。例如,上面的结构可以重写成:

struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;

注意:这个类型只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型。且位域中的位宽度必须小于或等于指定类型的位宽度。

所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

位域定义与结构定义相仿,其形式为:

struct 位域结构名 
{

位域列表 // 类型说明符 位域名: 位域长度

};

如下

struct bs{
int a:8;
int b:2;
int c:6;
}data;

说明 data 为 bs 变量,共占两个字节。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。

如下实例

struct packed_struct {
unsigned int f1:1;
unsigned int f2:1;
unsigned int f3:1;
unsigned int f4:1;
unsigned int type:4;
unsigned int my_int:9;
} pack;

在这里,packed_struct 包含了 6 个成员:四个 1 位的标识符 f1..f4、一个 4 位的 type 和一个 9 位的 my_int。

位域的使用

main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;

bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */


pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */
pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
}

typedef 关键字

C 语言提供了 typedef 关键字,可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:

typedef unsigned char BYTE;

在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:

BYTE  b1, b2;

这个关键字搭配结构体使用则是用来定义新的结构体名称

#include <stdio.h>
#include <string.h>

typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;

int main( )
{
Book book;

strcpy( book.title, "C 教程");
strcpy( book.author, "Runoob");
strcpy( book.subject, "编程语言");
book.book_id = 12345;

printf( "书标题 : %s\n", book.title);
printf( "书作者 : %s\n", book.author);
printf( "书类目 : %s\n", book.subject);
printf( "书 ID : %d\n", book.book_id);

return 0;
}

错误处理

C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。

在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。

可以在 errno.h 头文件中找到各种各样的错误代码。

所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。开发人员应该在程序初始化时,把 errno 设置为 0,这是一种良好的编程习惯。0 值表示程序中没有错误。

C 语言提供了 perror()strerror() 函数来显示与 errno 相关的文本消息。

perror() 函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。 strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。

#include <errno.h>
#include <string.h>

extern int errno ;

int main ()
{
FILE * pf;
int errnum;
pf = fopen ("unexist.txt", "rb");
if (pf == NULL)
{
errnum = errno;
fprintf(stderr, "错误号: %d\n", errno);
perror("通过 perror 输出错误");
fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));
}
else
{
fclose (pf);
}
return 0;
}

输出为如下

错误号: 2
通过 perror 输出错误: No such file or directory
打开文件错误: No such file or directory

退出状态码

在进行除法运算时,如果不检查除数是否为零,则会导致一个运行时错误。

为了避免这种情况发生,下面的代码在进行除法运算前会先检查除数是否为零:

#include <stdio.h>
#include <stdlib.h>

main()
{
int dividend = 20;
int divisor = 0;
int quotient;

if( divisor == 0){
fprintf(stderr, "除数为 0 退出运行...\n");
exit(-1);
}

quotient = dividend / divisor;
fprintf(stderr, "quotient 变量的值为 : %d\n", quotient );

exit(0);
}

通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS。在这里,EXIT_SUCCESS 是宏,它被定义为 0。

如果程序中存在一种错误情况,当您退出程序时,会带有状态值 EXIT_FAILURE,被定义为 -1。所以,上面的程序可以写成:

#include <stdio.h>
#include <stdlib.h>

main()
{
int dividend = 20;
int divisor = 5;
int quotient;

if( divisor == 0){
fprintf(stderr, "除数为 0 退出运行...\n");
exit(EXIT_FAILURE);
}
quotient = dividend / divisor;
fprintf(stderr, "quotient 变量的值为: %d\n", quotient );

exit(EXIT_SUCCESS);
}